#ifndef __M_ROOM_HPP__
#define __M_ROOM_HPP__

#include <iostream>
#include <vector>
#include <unordered_map>
#include <mutex>
#include <memory>
#include "db.hpp"
#include "logger.hpp"
#include "online.hpp"
#include "util.hpp"

#define BOARD_ROW 15
#define BOARD_COL 15
#define CHESS_WHITE 1
#define CHESS_BLACK 2

typedef enum
{
    GAME_START,
    GAME_OVER
} room_statu;

//定义一个游戏房间类，实现游戏房间内下棋和聊天动作的管理
class room
{
private:
    bool five(int row, int col, int row_off, int col_off, int color)
    {
        // row和col是当前下棋位置，row_off和col_off是偏移量，同时也是控制方向
        int chess_count = 1; // 直接将当前本身棋子算入
        //以横行为例，向右移动
        int search_row = row + row_off;
        int search_col = col + col_off;
        while (search_row >= 0 && search_row < BOARD_ROW && search_col >= 0 && search_col < BOARD_COL && _board[search_row][search_col] == color)
        {
            search_row += row_off;
            search_col += col_off;
            chess_count++;
        }
        //以横行为例，向左移动
        search_row = row - row_off;
        search_col = col - col_off;
        while (search_row >= 0 && search_row < BOARD_ROW && search_col >= 0 && search_col < BOARD_COL && _board[search_row][search_col] == color)
        {
            search_row -= row_off;
            search_col -= col_off;
            chess_count++;
        }
        return chess_count>=5;
    }

    uint64_t check_win(int row, int col, int color)
    {
        // 从下棋位置的四个方向依次判断是否出现五星连珠的情况(横行，竖列，正斜，反斜)
        if (five(row, col, 0, 1, color) || five(row, col, 1, 0, color) || five(row, col, -1, 1, color) || five(row, col, -1, -1, color))
        {
            // 只要任意一个方向上出现了五星连珠的情况，那就返回对应的uid
            return color == CHESS_WHITE ? _white_uid : _black_uid;
        }
        return 0;
    }

public:
    room(uint64_t room_id, user_table *tb_user, online_manager *online_user)
        : _room_id(room_id), _statu(GAME_START), _player_count(0), _tb_user(tb_user), _online_user(online_user), _board(BOARD_ROW, std::vector<int>(BOARD_COL, 0))
    {
        DLOG("%lu room create success!!!", _room_id);
    }

    ~room()
    {
        DLOG("%lu room destroy success!!!", _room_id);
    }

    /*获取游戏房间ID*/
    uint64_t id() { return _room_id; }
    /*获取游戏房间当前状态*/
    room_statu statu() { return _statu; }
    /*获取当前游戏房间内用户在线数*/
    int player_count() { return _player_count; }
    /*添加执白棋用户*/
    void add_white_user(uint64_t uid)
    {
        _white_uid = uid;
        _player_count++;
    }
    /*添加执黑棋用户*/
    void add_black_user(uint64_t uid)
    {
        _black_uid = uid;
        _player_count++;
    }
    /*获取执黑棋用户ID*/
    uint64_t get_black_user() { return _black_uid; }
    // 获取执白棋用户ID
    uint64_t get_white_user() { return _white_uid; }

    /*----------------------------------------------*/
    /*走棋Json格式
    //请求格式
    {
        "optype": "put_chess",  //put_chess表示当前是下棋请求
        "room_id": 111,         //room_id表示当前下棋动作是属于哪个游戏房间的
        "uid": 1,               //uid表示当前下棋动作是属于哪个用户的
        "row": 2,               //row表示当前下棋的行号
        "col": 2                //col表示当前下棋的列号
    }
    //响应格式(走棋失败格式)
    {
        "optype": "put_chess",
        "result":false,
        "reason":"走棋失败的原因"
    }
    //响应格式(走棋成功格式)
    {
        "optype": "put_chess",
        "result": true,
        "reason": "对方掉线，不战而胜！" / "对方/己方五星连珠，战无敌/虽败犹荣！",
        "room_id": 111,
        "uid": 1,
        "row": 2,
        "col": 2,
        "winner": 0  (0-未分胜负，!0-已分胜负(uid是谁，谁就胜利了))
    }
    */
    /*处理下棋动作*/
    Json::Value handle_chess(const Json::Value &req)
    {
        Json::Value rsp = req;
        int row = req["row"].asInt();
        int col = req["col"].asInt();
        uint64_t cur_uid = req["uid"].asInt64();
        // 判断游戏房间内两个玩家是否在线，任意一方没在线，另一方获胜
        if (_online_user->is_in_game_room(_white_uid) == false)
        {
            rsp["result"] = true;
            rsp["reason"] = "对方掉线，不战而胜";
            rsp["winner"] = (Json::UInt64)_black_uid;
            return rsp;
        }
        if (_online_user->is_in_game_room(_black_uid) == false)
        {
            rsp["result"] = true;
            rsp["reason"] = "对方掉线，不战而胜";
            rsp["winner"] = (Json::UInt64)_white_uid;
            return rsp;
        }
        // 获取走棋位置，判断当前位置是否被合理
        if (_board[row][col] != 0)
        {
            rsp["result"] = false;
            rsp["reason"] = "当前位置已经有其他棋子了！";
            return rsp;
        }
        // 判断当前走棋下，是否有玩家获胜
        int cur_color = cur_uid == _white_uid ? CHESS_WHITE : CHESS_BLACK;
        _board[row][col] = cur_color;
        uint64_t winner_uid = check_win(row, col, cur_color);
        if (winner_uid != 0)
            rsp["reason"] = "五星连珠，战无敌！";
        rsp["result"] = true;
        rsp["winner"] = (Json::UInt64)winner_uid;
        return rsp;
    }

    /*处理聊天动作*/
    /*聊天格式
    //聊天请求格式
    {
        "optype": "chat",
        "room_id": 111,
        "uid": 1,
        "message": "我等得花都谢了"
    }
    //聊天响应格式(失败格式)
    {
        "optype": "chat",
        "result": false,
        "reason": "聊天失败原因....比如有敏感词...."
    }
    //聊天响应格式(成功格式)
    {
        "optype": "chat",
        "result": true,
        "room_id": 111,
        "uid": 1,
        "message": "我等得花都谢了"
    }
    */
    Json::Value handle_chat(const Json::Value &req)
    {
        Json::Value rsp = req;
        // 检测消息中是否具有敏感词汇
        std::string message = req["message"].asString();
        size_t pos = message.find("垃圾");
        if (pos != std::string::npos)
        {
            rsp["result"] = false;
            rsp["reason"] = "有敏感词";
            return rsp;
        }
        // 将消息返回，进行广播群发
        rsp["result"] = true;
        return rsp;
    }

    /*处理玩家退出房间动作 (可以扩展到观战用户退出，扩展观战功能)*/
    void handle_exit(uint64_t uid)
    {
        // 如果是在下棋中下棋用户退出，则另一方获胜，下棋结束，否则是正常退出
        Json::Value rsp;
        if (_statu == GAME_START)
        {
            uint64_t winner_id = uid == _white_uid ? _black_uid : _white_uid;
            rsp["optype"] = "put_chess";
            rsp["result"] = true;
            rsp["reason"] = "对方退出，不战而胜";
            rsp["room_id"] = (Json::UInt64)_room_id;
            rsp["uid"] = (Json::UInt64)uid;
            rsp["row"] = -1;
            rsp["col"] = -1;
            rsp["winner"] = (Json::UInt64)winner_id;
            // 更新各自数据
            uint64_t loser_id = winner_id == _white_uid ? _black_uid : _white_uid;
            _tb_user->win(winner_id);
            _tb_user->lose(loser_id);
            _statu = GAME_OVER;
            broadcast(rsp);
        }
        _player_count--;
        return;
    }

    /*总的处理函数，在函数内部，区别请求类型，根据不同的类型去调用不同的函数处理，得到响应的信息进行广播*/
    void handle_request(const Json::Value &req)
    {
        // 验证房间号是否匹配
        Json::Value rsp;
        uint64_t room_id = req["room_id"].asUInt64();
        if (room_id != _room_id)
        {
            rsp["optype"] = req["optype"].asString();
            rsp["result"] = false;
            rsp["reason"] = "房间号不匹配！";
            broadcast(rsp);
            return;
        }
        // 根据不同的请求类型调用不同的处理函数
        std::string optype = req["optype"].asString();
        if (optype == "put_chess")
        {
            rsp = handle_chess(req);
            if (rsp["winner"].asUInt64() != 0)
            {
                // 更新各自数据
                uint64_t winner_id = rsp["winner"].asUInt64();
                uint64_t loser_id = winner_id == _white_uid ? _black_uid : _white_uid;
                _tb_user->win(winner_id);
                _tb_user->lose(loser_id);
                _statu = GAME_OVER;
            }
        }
        else if (optype == "chat")
        {
            rsp = handle_chat(req);
        }
        else
        {
            rsp["optype"] = req["optype"].asString();
            rsp["result"] = false;
            rsp["reason"] = "请求类型未知！";
        }
        // 将指定信息广播给游戏房间内所有的玩家
        std::string body;
        json_util::serialize(rsp, body);
        DLOG("房间-广播动作:%s", body.c_str());
        broadcast(rsp);
        return;
    }

    /*将指定的信息广播给游戏房间内的所有用户*/
    void broadcast(const Json::Value &rsq)
    {
        // 将对应的Json的数据格式序列化为字符串
        std::string content;
        json_util::serialize(rsq, content);
        // 获取该游戏房间内所有用户的通信链接
        // 发送响应信息
        wsserver_t::connection_ptr wconn = _online_user->get_conn_from_room(_white_uid);
        if (wconn.get() != nullptr)
            wconn->send(content);
        else
            DLOG("游戏房间-白棋玩家链接获取失败!!!");
        wsserver_t::connection_ptr bconn = _online_user->get_conn_from_room(_black_uid);
        if (bconn.get() != nullptr)
            bconn->send(content);
        else
            DLOG("游戏房间-黑棋玩家链接获取失败!!!");
        return;
    }

private:
    uint64_t _room_id;                    // 游戏房间ID
    room_statu _statu;                    // 当前游戏房间状态
    int _player_count;                    // 游戏房间内在线用户数
    uint64_t _white_uid;                  // 执白棋用户ID
    uint64_t _black_uid;                  // 执黑棋用户ID
    user_table *_tb_user;                 // 数据库操作指针
    online_manager *_online_user;         // 在线用户操作指针
    std::vector<std::vector<int>> _board; // 棋盘
};

//实现一个游戏房间管理类，以统一的方式实现游戏房间的增删等管理操作
using room_ptr=std::shared_ptr<room>;
class room_manager
{
public:
    room_manager(user_table* tb_user,online_manager* online_user)
        :_next_rid(1)
        ,_tb_user(tb_user)
        ,_online_user(online_user)
    {
        DLOG("游戏房间管理模块初始化完毕!!!");
    }
    ~room_manager()
    {
        DLOG("游戏房间管理模块销毁!!!");
    }

    /*创建游戏房间*/
    room_ptr create_room(uint64_t uid1,uint64_t uid2)
    {
        //1.判断两个用户在游戏大厅中是否在线，只有两个用户都在线才能创建房间
        if(_online_user->is_in_game_hall(uid1)==false)
        {
            DLOG("用户:%lu 不在游戏大厅中，游戏房间创建失败!!!",uid1);
            return room_ptr();
        }
        if(_online_user->is_in_game_hall(uid2)==false)
        {
            DLOG("用户:%lu 不在游戏大厅中，游戏房间创建失败!!!",uid2);
            return room_ptr();
        }

        //2.创建游戏房间，并将用用户信息添加进房间
        std::unique_lock<std::mutex> locK(_mutex);//加锁保护
        room_ptr rp(new room(_next_rid,_tb_user,_online_user));
        rp->add_white_user(uid1);//添加白棋玩家
        rp->add_black_user(uid2);//添加黑棋玩家
        //3.将游戏房间统一管理起来
        _rooms.insert(std::make_pair(_next_rid,rp));
        _users.insert(std::make_pair(uid1,_next_rid));
        _users.insert(std::make_pair(uid2,_next_rid));
        _next_rid++;
        //4.返回游戏房间信息
        return rp;
    }

    /*通过游戏房间ID获取游戏房间信息*/
    room_ptr get_room_by_rid(uint64_t rid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto iter=_rooms.find(rid);
        if(iter==_rooms.end()) return room_ptr();
        return iter->second;
    }

    /*通过用户ID获取游戏房间信息*/
    room_ptr get_room_by_uid(uint64_t uid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        //1.通过用户ID获取游戏房间ID
        auto uiter=_users.find(uid);
        if(uiter==_users.end()) return room_ptr();
        //2.通过游戏房间ID获取游戏房间信息
        auto riter=_rooms.find(uiter->second);
        if(riter==_rooms.end()) return room_ptr();
        return riter->second;
    }

    /*通过游戏房间ID销毁房间*/
    void remove_room(uint64_t rid)
    {
        //游戏房间信息都是通过shared_ptr进行管理的，因此只要将shared_ptr从_rooms中移除即可
        //当shared_ptr中的计数器为0的时候，自然会析构
        //1.通过游戏房间ID获取游戏房间信息
        room_ptr rp=get_room_by_rid(rid);
        if(rp.get()==nullptr) return;
        //2.通过游戏房间信息获取房间内所有用户ID
        uint64_t uid1=rp->get_white_user();
        uint64_t uid2=rp->get_black_user();
        //3.移除房间管理中的用户信息
        std::unique_lock<std::mutex> lock(_mutex);
        _users.erase(uid1);
        _users.erase(uid2);
        //4.移除房间管理信息
        _rooms.erase(rid);
    }

    /*删除游戏房间中的指定用户，如果房间中没有用户了，则销毁房间(该函数在用户连接断开时调用)*/
    void remove_room_user(uint64_t uid)
    {
        //1.通过用户ID获取游戏房间信息
        room_ptr rp=get_room_by_uid(uid);
        if(rp.get()==nullptr) return;
        //2.用户退出游戏房间的动作
        rp->handle_exit(uid);
        //3.判断游戏房间内是否还有用户
        if(rp->player_count()==0)
        {
            //销毁房间
            remove_room(rp->id());
        }
        return;
    }

private:
    uint64_t _next_rid;//用于分配房间ID的自增长ID
    std::mutex _mutex;//加锁保护数据
    user_table* _tb_user;//操作数据库的句柄
    online_manager* _online_user;//在线用户管理的句柄
    std::unordered_map<uint64_t,room_ptr> _rooms;//建立游戏房间ID与游戏房间的映射关系，统一管理所有的游戏房间
    std::unordered_map<uint64_t,uint64_t> _users;//建立用户ID与游戏房间ID的映射关系，通过用户ID可以找到其所在的游戏房间
};

#endif